package com.ficus.road.maintain.service.coverage;

import com.ficus.road.maintain.core.model.*;
import com.ficus.road.maintain.core.model.coverage.RoadCoverageYT;
import com.ficus.road.maintain.core.model.zh.UnitScore;
import com.ficus.road.maintain.dao.CompanyDao;
import com.ficus.road.maintain.dao.RoadInfoDao;
import com.ficus.road.maintain.dao.UnitDao;
import com.ficus.road.maintain.dao.UnitScoreDao;
import com.ficus.road.maintain.dao.coverage.RoadCoverageYTDao;
import com.ficus.road.maintain.service.CycleNumService;
import com.ficus.road.maintain.service.TheoryGpsService;
import com.ficus.road.maintain.util.MyDateUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.ficus.road.maintain.service.coverage.YtCoverageTask.COVERAGE_THRESHOLD;

@Service
@Slf4j
@RequiredArgsConstructor
public class YTCoverageService {
    private final CycleNumService cycleNumService;
    private final RoadCoverageYTDao roadCoverageYTDao;
    private final CompanyDao companyDao;
    private final RoadInfoDao roadInfoDao;
    private final UnitDao unitDao;
    private final UnitScoreDao unitScoreDao;
    private final TheoryGpsService theoryGpsService;
    Map<String, List<TheoryGps>> roadTheoryMap;
    private Cache<String, List<CompanyCoverRateInfoResponse>> companyCoverageCache;

    @PostConstruct
    public void init() {
        roadTheoryMap = theoryGpsService.findAll().stream().collect(Collectors.groupingBy(TheoryGps::getRoadInfoId));
        companyCoverageCache = Caffeine.newBuilder()
                // 设置最后一次写入或访问后经过固定时间过期
                .expireAfterWrite(1, TimeUnit.MINUTES)
                // 初始的缓存空间大小
                .initialCapacity(100)
                // 缓存的最大条数
                .maximumSize(10000)
                .build();
    }


    public List<UnitCoverRateResponse> getUnitCoverage(Set<String> unitIdSet, String currentCycleNum) {
        List<String> lastCycleNum;
        String currentCycleNum1 = cycleNumService.getCurrentCycleNum();
        if ("0".equals(currentCycleNum) || currentCycleNum1.equals(currentCycleNum)) {
            lastCycleNum = cycleNumService.getLast2CycleNum();
        } else {
            lastCycleNum = Lists.newArrayList(currentCycleNum);
        }
        Map<String, String> unitCompanyMap = Maps.newHashMap();
        List<Company> companyList = companyDao.findByQuery(new Query(Criteria.where("units").in(unitIdSet)));
        companyList.forEach(company -> company.getUnits().forEach(unitId -> unitCompanyMap.put(unitId, company.getName())));
        List<Unit> units = unitDao.findByQuery(new Query(Criteria.where("_id").in(unitIdSet)));
        Map<String, List<RoadCoverageYT>> unitIdCoverMap = roadCoverageYTDao.findByQuery(new Query(Criteria.where("cycle_num")
                .in(lastCycleNum).and("unit_id").in(unitIdSet))).stream().collect(Collectors.groupingBy(RoadCoverageYT::getUnitId));
        List<RoadInfo> roadInfoList = roadInfoDao.findByQuery(new Query(Criteria.where("coordinates").exists(true).ne(null)
                .and("unit_id").in(unitIdSet)));
        Map<String, List<RoadInfo>> unitRoadMap = roadInfoList.stream().collect(Collectors.groupingBy(RoadInfo::getUnitId));
        return units.stream().map(unit -> {
                    List<RoadCoverageYT> roadCoverageYTS = unitIdCoverMap.getOrDefault(unit.getId(), Lists.newArrayList());
                    UnitCoverRateResponse response = new UnitCoverRateResponse();
                    response.setUnitId(unit.getId());
                    response.setUnitName(unit.getUnitName());
                    response.setCompany(unitCompanyMap.get(unit.getId()));
                    response.setRoadType(unit.getRoadType());
                    int shouldCoverage = unitRoadMap.getOrDefault(unit.getId(), Lists.newArrayList())
                            .stream().mapToInt(r -> roadTheoryMap.getOrDefault(r.getId(), Lists.newArrayList()).size()).sum();
                    if ("0".equals(currentCycleNum) || currentCycleNum1.equals(currentCycleNum)) {
                        String todayStr = MyDateUtil.dateFormatStr(new Date(), "yyyy-MM-dd");
                        List<RoadCoverageYT> todayCoverage = roadCoverageYTS.stream().filter(t -> t.getDay().equals(todayStr)).collect(Collectors.toList());
                        int realCoverageToday = todayCoverage.stream()
                                .map(t -> t.getCoverageLocationIndex().stream().map(index -> t.getRoadId() + index).collect(Collectors.toSet()))
                                .flatMap(Collection::stream)
                                .collect(Collectors.toSet())
                                .size();

                        response.setTodayRate(shouldCoverage == 0 ? "0" : String.valueOf((double) realCoverageToday / shouldCoverage));

                        List<RoadCoverageYT> collectCurrentCycle = roadCoverageYTS.stream().filter(t -> t.getCycleNum().equals(lastCycleNum.get(0))).collect(Collectors.toList());
                        int realCoverageCurrentCycle = collectCurrentCycle.stream()
                                .map(t -> t.getCoverageLocationIndex().stream().map(index -> t.getRoadId() + index).collect(Collectors.toSet()))
                                .flatMap(Collection::stream)
                                .collect(Collectors.toSet())
                                .size();
                        response.setWeekRate(shouldCoverage == 0 ? "0" : String.valueOf((double) realCoverageCurrentCycle / shouldCoverage));

                        List<RoadCoverageYT> collectLastCycle = roadCoverageYTS.stream().filter(t -> t.getCycleNum().equals(lastCycleNum.get(1))).collect(Collectors.toList());
                        int realCoverageLastCycle = collectLastCycle.stream()
                                .map(t -> t.getCoverageLocationIndex().stream().map(index -> t.getRoadId() + index).collect(Collectors.toSet()))
                                .flatMap(Collection::stream)
                                .collect(Collectors.toSet())
                                .size();
                        response.setLastWeekRate(shouldCoverage == 0 ? "0" : String.valueOf((double) realCoverageLastCycle / shouldCoverage));
                    } else {

                        int realCoverage = roadCoverageYTS.stream()
                                .map(t -> t.getCoverageLocationIndex().stream().map(index -> t.getRoadId() + index).collect(Collectors.toSet()))
                                .flatMap(Collection::stream)
                                .collect(Collectors.toSet())
                                .size();
                        response.setWeekRate(shouldCoverage == 0 ? "0" : String.valueOf((double) realCoverage / shouldCoverage));
                    }
                    return response;
                })
                .sorted(Comparator.comparing(UnitCoverRateResponse::getUnitId))
                .collect(Collectors.toList());
    }

    public List<CompanyCoverRateInfoResponse> getCompanyCoverRates(String companyId, String cycleNum) {
        List<CompanyCoverRateInfoResponse> ifPresent = companyCoverageCache.getIfPresent(companyId + cycleNum);
        if (null != ifPresent) {
            return ifPresent;
        }
        String yearMonth = MyDateUtil.dateFormatStr(MyDateUtil.getFirstDayOfLastMonth(), "yyyy-MM");
        Company fetch = companyDao.fetch(companyId);
        if (Objects.isNull(fetch)) {
            return Lists.newArrayList();
        }
        Map<String, List<RoadInfo>> unitRoadMap = roadInfoDao.findByQuery(new Query(Criteria.where("unit_id").in(fetch.getUnits())
                        .and("coordinates").exists(true).ne(null)))
                .stream().collect(Collectors.groupingBy(RoadInfo::getUnitId));
        Map<String, List<RoadCoverageYT>> unitIdCoverMap = roadCoverageYTDao.findByQuery(new Query(Criteria.where("cycle_num")
                .is(cycleNum).and("unit_id").in(fetch.getUnits()))).stream().collect(Collectors.groupingBy(RoadCoverageYT::getUnitId));
        Map<String, Double> unitScoreMap = unitScoreDao.findByQuery(new Query(Criteria.where("unit_id").in(fetch.getUnits()).and("date").is(yearMonth)))
                .stream().collect(Collectors.toMap(UnitScore::getUnitId, UnitScore::getTotal));
        List<CompanyCoverRateInfoResponse> result = unitDao.findByQuery(new Query(Criteria.where("_id").in(fetch.getUnits()))).stream().map(unit -> {
                    List<RoadInfo> roadInfoList = unitRoadMap.getOrDefault(unit.getId(), Lists.newArrayList());
                    int shouldCoverage = roadInfoList.stream().mapToInt(r -> roadTheoryMap.getOrDefault(r.getId(), Lists.newArrayList()).size()).sum();
                    CompanyCoverRateInfoResponse companyCoverRateInfoResponse = new CompanyCoverRateInfoResponse();
                    companyCoverRateInfoResponse.setUnitId(unit.getId());
                    companyCoverRateInfoResponse.setUnitName(unit.getUnitName());
                    companyCoverRateInfoResponse.setRoadType(unit.getRoadType());
                    companyCoverRateInfoResponse.setScore(unitScoreMap.get(unit.getId()));
                    List<RoadCoverageYT> roadCoverageYTS = unitIdCoverMap.getOrDefault(unit.getId(), Lists.newArrayList());
                    int realCoverage = roadCoverageYTS.stream()
                            .map(t -> t.getCoverageLocationIndex().stream().map(index -> t.getRoadId() + index).collect(Collectors.toSet()))
                            .flatMap(Collection::stream)
                            .collect(Collectors.toSet())
                            .size();
                    double coverageCurrentCycle = shouldCoverage == 0 ? 0 : (double) realCoverage / shouldCoverage;
                    companyCoverRateInfoResponse.setWeekRate(String.valueOf(coverageCurrentCycle));
                    Map<String, List<RoadCoverageYT>> roadIdCoverMap = roadCoverageYTS.stream().collect(Collectors.groupingBy(RoadCoverageYT::getRoadId));
                    List<RoadCoverDetail> roadCoverDetails = roadInfoList.stream().map(roadInfo -> {
                        RoadCoverDetail roadCoverDetail = new RoadCoverDetail();
                        roadCoverDetail.setRoadId(roadInfo.getId());
                        roadCoverDetail.setRoadName(roadInfo.getRoadName());
                        roadCoverDetail.setCoordinates(roadInfo.getCoordinates());
                        double roadLen = Double.parseDouble(roadInfo.getSecLength());
                        int shouldCoverSize = roadTheoryMap.getOrDefault(roadInfo.getId(), Lists.newArrayList()).size();
                        int realRoadCoverageSize = roadIdCoverMap.get(roadInfo.getId()).stream()
                                .map(RoadCoverageYT::getCoverageLocationIndex)
                                .flatMap(Collection::stream)
                                .collect(Collectors.toSet())
                                .size();
                        double coverage = shouldCoverSize == 0 ? 0 : (double) realRoadCoverageSize / shouldCoverSize;
                        roadCoverDetail.setCoverRate(String.valueOf(coverage));
                        roadCoverDetail.setWorkLengthAll(roadLen);
                        roadCoverDetail.setWorkLength(roadLen * coverage);
                        roadCoverDetail.setStatus(coverage > COVERAGE_THRESHOLD ? RoadCoverDetail.RoadCoverageStatusEnum.COVER : RoadCoverDetail.RoadCoverageStatusEnum.UNCOVER);
                        return roadCoverDetail;
                    }).collect(Collectors.toList());
                    companyCoverRateInfoResponse.setRoads(roadCoverDetails);
                    companyCoverRateInfoResponse.setDiseaseCount("0");
                    double workLenAll = roadCoverDetails.stream().mapToDouble(RoadCoverDetail::getWorkLengthAll).sum();
                    companyCoverRateInfoResponse.setShouldCover(String.valueOf(workLenAll));
                    companyCoverRateInfoResponse.setRealCover(String.valueOf(workLenAll * coverageCurrentCycle));
                    return companyCoverRateInfoResponse;
                }).sorted(Comparator.comparing(CompanyCoverRateInfoResponse::getUnitId))
                .collect(Collectors.toList());

        companyCoverageCache.put(companyId + cycleNum, result);
        return result;
    }
}
